package com.kanas.webak.rbac.service;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.inject.Singleton;
import javax.servlet.ServletContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.context.ServletContextAware;

import com.kanas.framework.support.LoggerSupport;
import com.kanas.framework.system.Function;
import com.kanas.framework.system.FunctionGroup;
import com.kanas.framework.web.context.WebAppContext;
import com.kanas.framework.web.context.support.WebAppContextUtils;
import com.kanas.webak.rbac.domain.AppPermission;
import com.kanas.webak.rbac.domain.AppRole;
import com.kanas.webak.rbac.repository.PermissionRepository;
import com.kanas.webak.rbac.repository.RoleRepository;

@Service("kanas.rbac.service.permissionservice")
@Singleton
public class AppPermissionServiceImpl extends LoggerSupport implements AppPermissionService,FilterInvocationSecurityMetadataSource,ServletContextAware {

	private Boolean dirty = true;
	private ServletContext servletContext;
	private WebAppContext context;
	
	@Autowired
	private PermissionRepository permissionRepository;
	
	@Autowired
	private RoleRepository roleRepository;

	//support spring security
	private PathMatcher urlMatcher = new AntPathMatcher();
	
	//A mapping of function to its roles.
	private static Map<String,Collection<ConfigAttribute>> resourceMap = new HashMap<String,Collection<ConfigAttribute>>();

	//all roles in system
	private static Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
	
	public Set<FunctionGroup> findAllFunctionGroups(AppRole role, Set<AppRole> coveredRoles){
		if(null==coveredRoles)coveredRoles=new HashSet<AppRole>();
		Set<FunctionGroup> groups = new HashSet<FunctionGroup>();
		FunctionGroup fg;
		Collection<AppRole> extensionRoles = new HashSet<AppRole>();
		extensionRoles.add(role);
		for(AppRole r:roleRepository.findByExtensionRolesInAndEnableFlag(extensionRoles, true)){
			if(coveredRoles.contains(r)||!r.getEnableFlag())return new HashSet<FunctionGroup>();
			coveredRoles.add(r);
			groups.addAll(findAllFunctionGroups(r,coveredRoles));
			for(AppPermission p:permissionRepository.findByRole(r)){
				fg=context.getFunctionGroup(p.getPermissionId());
				if(null!=fg&&!groups.contains(fg))groups.add(fg);
			}
		}
		return groups;
	}
	
	private Collection<ConfigAttribute> roleToConfigAttribute(Collection<AppRole> roles){
		Set<ConfigAttribute> attrs = new HashSet<ConfigAttribute>();
		for(AppRole r:roles)
			attrs.add(new SecurityConfig(AppRole.ROLE_PREFIX+r.getId()));
		return attrs;
	}
	
	@Override
	@Transactional(readOnly = true,propagation=Propagation.REQUIRED)
	@PostConstruct
	public void init() throws Exception{
		synchronized(dirty){
			resourceMap.clear();
			allAttributes.clear();
			Collection<AppRole> adminRoles = roleRepository.findByExportFlagAndEnableFlagAndAdminFlag(true, true, true);
			for(Function func:context.listFunctions()){
				resourceMap.put(func.getURI(), roleToConfigAttribute(adminRoles));
			}
			for(AppRole ar:roleRepository.findByExportFlagAndEnableFlagAndAdminFlag(true,true,false)){
				for(FunctionGroup group:findAllFunctionGroups(ar,null)){
					for(Function func:group.getFunctions()){
						resourceMap.get(func.getURI()).add(new SecurityConfig(AppRole.ROLE_PREFIX+ar.getId()));
					}
				}
			}
		}
		dirty=false;
	}
	
	@Override
	@Transactional
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		if(!allAttributes.isEmpty())return allAttributes;
		for(Map.Entry<String, Collection<ConfigAttribute>> entry:resourceMap.entrySet()){
			allAttributes.addAll(entry.getValue());
		}
		return allAttributes;
	}
	
	/**
	 * Return the configAttributes(roles) of matched URI
	 */
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object)
			throws IllegalArgumentException {
		String url=((FilterInvocation)object).getRequestUrl();
		String method=((FilterInvocation)object).getHttpRequest().getMethod();
		this.logDebug("security: access uri is '",Function.getURI(url, method));
		for(String resUri:resourceMap.keySet()){
			this.logDebug("security: config url is ",resUri);
			if(urlMatcher.match(resUri, Function.getURI(url, method))){
				this.logDebug("security: matched");
				this.logDebug("security: attributes: ",resourceMap.get(resUri).toString());
				return resourceMap.get(resUri);
			}
		}
		return null;
	}

	@Override
	public boolean supports(Class<?> clazz) {
		return true;
	}

	@Override
	public void setServletContext(ServletContext servletContext) {
		this.servletContext=servletContext;
		context = WebAppContextUtils.getWebApplicationContext(this.servletContext);
	}

	public PermissionRepository getPermissionRepository() {
		return permissionRepository;
	}

	public void setPermissionRepository(PermissionRepository permissionRepository) {
		this.permissionRepository = permissionRepository;
	}
	
	public RoleRepository getRoleRepository() {
		return roleRepository;
	}

	public void setRoleRepository(RoleRepository roleRepository) {
		this.roleRepository = roleRepository;
	}

	public boolean isDirty() {
		return dirty;
	}

	public void dirty() {
		synchronized(dirty){
			this.dirty = true;
		}
	}
	
}